//////////////////////////////////////////////////////////////////////////////////////
// Description:
//     Provides formatted output
//
// Legal Notices:
//     Copyright (c) 2008, Telliam Consulting, LLC.
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//
//     * Neither the name of Telliam Consulting nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// Disclaimer:
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.  
//
namespace FusionWare
{
    namespace FormattedOutput
    {
        template <typename CharType>
        const CharType* ParseFmtValue(const CharType* pFmt, int *pValue, va_list *ppParams) 
        {
            if(*pFmt == (CharType)'*')
            {
                *pValue = va_arg(*ppParams, int);
                pFmt++;
            }
            else
            {
                int i = 0;
                while( *pFmt >= (CharType)'0' && *pFmt<= (CharType)'9')
                {
                    i = i * 10 + (*pFmt - (CharType)'0');
                    pFmt++;
                }
                *pValue = i;
            }
            
            return pFmt;
        }

        struct FormatSpec
        {
            bool LeftJustify;
            bool ShowSign;
            bool ShowPrefix;
            bool UpperCase;
            int FillChar;
            int Width;
            int Precision;
            int Size;
            int Radix;
        };

        // %[flags] [width] [.precision] [{h | l | I | I32 | I64}]type
        // pFmt points to the first character after the '%'
        // return is the pointer to the first non-spec character in the string
        // (usually the type character for the spec)
        //
        template<typename CharType>
        CharType* ParseFormatSpec(CharType* pFmt, FormatSpec& FormatInfo, va_list* ppParams)
        {
            FormatInfo.LeftJustify = false;
            FormatInfo.ShowPrefix = false;
            FormatInfo.Precision = -1;
            FormatInfo.Width = -1;
            FormatInfo.Size = 1;

            while(*++pFmt)
            {
                if(*pFmt==(CharType)'-')
                    FormatInfo.LeftJustify = true;
                else if(*pFmt==(CharType)'#')
                    FormatInfo.ShowPrefix = true;
                else
                    break;
            }

            // Parse fill character 
            if(*pFmt==(CharType)'0')
            {
                FormatInfo.FillChar=(CharType)'0';
                pFmt++;
            }
            else
                FormatInfo.FillChar=(CharType)' ';

            // Parse the width specification
            pFmt=ParseFmtValue(pFmt, &FormatInfo.Width, ppParams);

            // Parse the precision spec
            if(*pFmt==(CharType)'.')
                pFmt=ParseFmtValue(++pFmt, &FormatInfo.Precision, ppParams);

            // Parse the operand size specifier if present
            if(*pFmt==(CharType)'l')
                pFmt++;
            else if (*pFmt==(CharType)'h')
            {
                FormatInfo.Size=0;
                pFmt++;
            }
            else if((*pFmt == (CharType)'I') && (*(pFmt+1) == (CharType)'6') && (*(pFmt+2) == (CharType)'4'))
            {
                pFmt+=3;
                FormatInfo.Size = 2;
            }

            return pFmt;
        }

        union Number
        {
            long l;
            unsigned long ul;
            __int64 i64;
            unsigned __int64 u64;
        };

        template<typename CharType>
        int GenerateDigits(Number Val, FormatSpec& FormatInfo, CharType* pDigitsBuf)
        {
            const CharType lowerval[] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
            const CharType upperval[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
            
            int charsWritten = 0;

            if(2==FormatInfo.Size)
            {
                do
                {
                    int digit = int(Val.u64 % FormatInfo.Radix);
                    pDigitsBuf[charsWritten++] = (FormatInfo.UpperCase ? upperval[digit] : lowerval[digit]);
                }while(Val.u64 /= FormatInfo.Radix);
            }
            else
            {
                do
                {
                    int digit = Val.ul % FormatInfo.Radix;
                    pDigitsBuf[charsWritten++] = (FormatInfo.UpperCase ? upperval[digit] : lowerval[digit]);
                }while(Val.ul /= FormatInfo.Radix);
            }
            
            for(int i=0; i< charsWritten/2; ++i)
            {
                CharType t;
                t = pDigitsBuf[i];
                pDigitsBuf[i] = pDigitsBuf[charsWritten-i-1];
                pDigitsBuf[charsWritten-i-1] = t;
            }

            // always terminate it (but don't include the \0 in the returned count)
            pDigitsBuf[charsWritten] = (CharType)0;
            
            return charsWritten;
        }

        template<typename CharType, typename OutputFunc>
        int FormatNumericValue(OutputFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams)
        {
            // temp buffer for the digits without padding
            CharType digitsBuf[32];
            int charsWritten = 0;

            Number val;
            int digitCount;

            // special cases to act like MSC v5.10
            if(FormatInfo.LeftJustify || FormatInfo.Precision >=0)
                FormatInfo.FillChar=(CharType)' ';

            if(1 == FormatInfo.Size)
                val.l=va_arg(*ppParams, long);
            else if (2 == FormatInfo.Size)
                val.i64 = va_arg(*ppParams, __int64);
            else if (FormatInfo.ShowSign)
                val.l=va_arg(*ppParams, short);
            else
                val.ul=va_arg(*ppParams, unsigned);

            if(FormatInfo.ShowSign && val.l<0L)
                val.l=-val.l;
            else
                FormatInfo.ShowSign = false;

            digitCount = GenerateDigits(val, FormatInfo, digitsBuf);

            FormatInfo.Width -= digitCount;
            FormatInfo.Precision -= digitCount;
            if(FormatInfo.Precision > 0)
                FormatInfo.Width -= FormatInfo.Precision;

            if(FormatInfo.ShowPrefix && FormatInfo.Radix==16)
            {
                FormatInfo.Width -=2;
                OutputChar((CharType)'0');
                OutputChar(FormatInfo.UpperCase ? (CharType)'X' : (CharType)'x');
                FormatInfo.ShowPrefix = false;
            }

            if(FormatInfo.ShowSign)
            {
                FormatInfo.ShowSign = false;
                OutputChar((CharType)'-');
                FormatInfo.Width--;
            }
           
            if(FormatInfo.LeftJustify)
            {
                // print precision fill digits
                for(int temp =charsWritten ;temp < FormatInfo.Precision; --FormatInfo.Precision)
                    OutputChar((CharType)'0');

                for(int i=0; digitsBuf[i]; ++i)
                    OutputChar(digitsBuf[i]);

                // pad to the right of the digits
                while(FormatInfo.Width-- > 0)
                    OutputChar(FormatInfo.FillChar);
            }
            else
            {
                // pad to the left of the digits
                while(FormatInfo.Width-- > 0)
                    OutputChar(FormatInfo.FillChar);

                // print precision fill digits
                for(int temp =charsWritten ;temp < FormatInfo.Precision; --FormatInfo.Precision)
                    OutputChar((CharType)'0');

                // print digits
                for(int i=0; digitsBuf[i]; ++i)
                    OutputChar(digitsBuf[i]);
            }

            return charsWritten;
        }

        // these inlines handle getting the string length in a type ambiguous manner

        inline int FormatStrLen(const WCHAR* pString) { return (int)wcslen(pString); }
        inline int FormatStrLen(const char* pString)  { return (int)strlen(pString); }

        // forward reference the string formatting template for "%s" for use
        // in the "%S" handlers below

        template<typename InCharType, typename OutCharType, typename OutputCharFunc>
        int FormatOutputString(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams);

        // specialized templates for handling the "%S" alternate string format
        // where the input string is the opposite of the normal one. 
        // these are done as class templates since partial specialization is
        // only allowed on class templates (not function templates)

        // this version provides the base for specialization - it won't actually be instantiated. 
        template <typename InCharType, typename OutCharType, typename OutputCharFunc>
        class TAlt
        {
        public:
            static inline int FormatOutputString(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams)
            {
            // ASSERT(FALSE);
                return 0;
            }
        };

        // specialized template for functions using WCHAR as the default for "%s"
        // simply wraps FormatOutputString with InCharType set to char so that "%S"
        // treats the arg as an ASCII string.
        template <typename OutCharType, typename OutputCharFunc>
        class TAlt<WCHAR, OutCharType, OutputCharFunc>
        {
        public:
            static inline int FormatOutputString(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams)
            {
                return ::FormatOutputString<char, OutCharType>(OutputChar, FormatInfo, ppParams);
            }
        };

        // specialized template for functions using char as the default for "%s"
        // simply wraps FormatOutputString with InCharType set to WCHAR so that "%S"
        // treats the arg as a WCHAR string.
        template <typename OutCharType, typename OutputCharFunc>
        class TAlt<char, OutCharType, OutputCharFunc>
        {
        public:
            static inline int FormatOutputString(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams)
            {
                return ::FormatOutputString<WCHAR, OutCharType>(OutputChar, FormatInfo, ppParams);
            }
        };

        // handles Type specific string formatting.
        // used for handling "%s" and "%S" escape sequences
        template<typename InCharType, typename OutCharType, typename OutputCharFunc>
        int FormatOutputString(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, va_list* ppParams)
        {
            int charsWritten = 0;
            InCharType* pOutString = va_arg(*ppParams, InCharType*);
            
            if(NULL == pOutString)
                return 0;

            int len = FormatStrLen(pOutString);

            if(FormatInfo.Precision >= 0 && len > FormatInfo.Precision)
                len=FormatInfo.Precision;

            FormatInfo.Width -= len;
            if(FormatInfo.LeftJustify)
            {
                for(; len-- ; ++charsWritten)
                    OutputChar((OutCharType)*pOutString++);
                
                for(; FormatInfo.Width-- > 0; ++charsWritten)
                    OutputChar((OutCharType)FormatInfo.FillChar);
            }
            else
            {
                for(; FormatInfo.Width-- > 0; ++charsWritten)
                    OutputChar((OutCharType)FormatInfo.FillChar);

                for(; len--; ++charsWritten)
                    OutputChar((OutCharType)*pOutString++);
            }

            return charsWritten;
        }

        // handles special formatting "Escape Codes"
        // Input:
        //     CharType - Function to output a character
        template<typename CharType, typename OutputCharFunc>
        static int FormatSpecialChar(OutputCharFunc& OutputChar, FormatSpec& FormatInfo, const CharType* pFmt, va_list* ppParams)
        {
            FormatInfo.UpperCase = false;
            FormatInfo.ShowSign = false;
            FormatInfo.Radix = 10;

            switch (*pFmt)
            {
                case 0:
                    return 0;

                case (CharType)'i':
                case (CharType)'d':
                    FormatInfo.ShowSign = true;

                case (CharType)'u':
                    return FormatNumericValue<CharType>(OutputChar, FormatInfo, ppParams);

                case (CharType) 'p' :
                    if(FormatInfo.Precision <= 0)
                        FormatInfo.Precision = 8;
                    FormatInfo.FillChar = '0';
                    FormatInfo.Radix = 16;
                    FormatInfo.UpperCase = true;
                    return FormatNumericValue<CharType>(OutputChar, FormatInfo, ppParams);
                
                case (CharType)'B':
                    if(FormatInfo.Width <= 0)
                        FormatInfo.Width = 2;
                    FormatInfo.FillChar = '0';
                    FormatInfo.Radix = 16;
                    FormatInfo.UpperCase = true;
                    return FormatNumericValue<CharType>(OutputChar, FormatInfo, ppParams);

                case (CharType)'H':
                    if(FormatInfo.Width <= 0)
                        FormatInfo.Width = 4;
                    FormatInfo.FillChar = '0';
                    FormatInfo.Radix = 16;
                    FormatInfo.UpperCase = true;
                    return FormatNumericValue<CharType>(OutputChar, FormatInfo, ppParams);

                case (CharType)'X':
                    FormatInfo.UpperCase = true;
                case (CharType)'x':
                    FormatInfo.Radix = 16;
                    return FormatNumericValue<CharType>(OutputChar, FormatInfo, ppParams);

                case (CharType)'c':
                        OutputChar(va_arg(*ppParams, CharType));
                        return 1;

                case (CharType)'a':   
                    // ascii string
                    return FormatOutputString<char, CharType>(OutputChar, FormatInfo, ppParams);

                case (CharType)'S':
                    return TAlt<CharType, CharType, OutputCharFunc>::FormatOutputString(OutputChar, FormatInfo, ppParams);

                case (CharType)'s':
                    return FormatOutputString<CharType, CharType, OutputCharFunc>(OutputChar, FormatInfo, ppParams);

                default:
                    // Output the unsupported char and continue 
                    OutputChar(*pFmt);    
                    return 1;
            }
        }

        // Template for implementing formatted print functions
        // this is an internal helper for implementing printf/sprintf
        // string formatting functions. This is highly flexible and 
        // re-usable By isolating the actual character output function
        // and the use of type specific templates. Thus no matter what
        // character type is used for the format string this template
        // (actually a set of related templates) handles it correctly.
        // This prevents making cut and paste copies of the code and
        // needing to maintain multiple variations of code for minor
        // updates. 
        template<typename CharType, typename OutputCharFunc>
        int FormatPrint(OutputCharFunc& OutputChar, const CharType* pFmt, va_list pParams) 
        {
            int retVal = 0;

            while(*pFmt)
            {
                // Anything that doesn't start with a % goes out raw.
                if(*pFmt==(CharType)'%')
                {
                    FormatSpec formatInfo;
                    pFmt = ParseFormatSpec(pFmt, formatInfo, &pParams);
                    
                    int outChars = FormatSpecialChar<CharType>(OutputChar, formatInfo, pFmt, &pParams);
                    pFmt++;
                    retVal += outChars;
                }
                else
                {
                    OutputChar(*pFmt);
                    ++retVal;
                    ++pFmt;
                }
            }

            return retVal;
        }

        //////////////////////////////////////////////////////////////////
        // Description:
        //   Class to implement buffered output
        //
        // Remarks:
        //   This class encapsulates the position within an output buffer
        //   and the actual writing to the buffer. It automatically terminates
        //   the buffer with a terminating \0 
        //   By using a class with a function call operator this class
        //   is usable with the format templates and prevents threading
        //   problems with using a static pointer for tracking the output
        //   position in the buffer across multiple subfunctions. 
        //
        // TODO:
        //   Add safety checking in this to automatically drop
        //   characters past end of the buffer and ensure the last character
        //   is always a null terminator.
        //
        template <class CharType>
        class BufferedOutput
        {
        public:
            BufferedOutput(CharType* pBuffer) { this->pBuffer = pBuffer; }
            ~BufferedOutput() { *pBuffer = 0;  }

            // provide fuction call operator to handle character
            // output
            void operator()(CharType c)
            {
                *pBuffer = c;
                ++pBuffer;
            }

        protected:
            CharType* pBuffer;
        };
    }
}
